repo: Add OstreeRepoRemoteChange replace operation
authorDan Nicholson <nicholson@endlessm.com>
Tue, 12 Sep 2017 17:23:31 +0000 (12:23 -0500)
committerAtomic Bot <atomic-devel@projectatomic.io>
Fri, 8 Feb 2019 14:36:41 +0000 (14:36 +0000)
Add the OSTREE_REPO_REMOTE_CHANGE_REPLACE operation to the
OstreeRepoRemoteChange enum. This operation will add a remote or replace
an existing one. It respects the location of the remote configuration
file when replacing and the remotes config dir settings when adding a
new remote.

Closes: #1166
Approved by: cgwalters

src/libostree/ostree-repo.c
src/libostree/ostree-repo.h
tests/test-remotes-config-dir.js

index fc8844812221f91068c110cb249fc7d211fe7053..37a6cdf0d252c7c219938ef3e9f8b3996c0e5a36 100644 (file)
@@ -1735,6 +1735,88 @@ ostree_repo_remote_delete (OstreeRepo     *self,
   return impl_repo_remote_delete (self, NULL, FALSE, name, cancellable, error);
 }
 
+
+static gboolean
+impl_repo_remote_replace (OstreeRepo     *self,
+                          GFile          *sysroot,
+                          const char     *name,
+                          const char     *url,
+                          GVariant       *options,
+                          GCancellable   *cancellable,
+                          GError        **error)
+{
+  g_return_val_if_fail (name != NULL, FALSE);
+  g_return_val_if_fail (url != NULL, FALSE);
+  g_return_val_if_fail (options == NULL || g_variant_is_of_type (options, G_VARIANT_TYPE ("a{sv}")), FALSE);
+
+  if (!ostree_validate_remote_name (name, error))
+    return FALSE;
+
+  g_autoptr(GError) local_error = NULL;
+  g_autoptr(OstreeRemote) remote = _ostree_repo_get_remote (self, name, &local_error);
+  if (remote == NULL)
+    {
+      if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_propagate_error (error, g_steal_pointer (&local_error));
+          return FALSE;
+        }
+      g_clear_error (&local_error);
+      if (!impl_repo_remote_add (self, sysroot, FALSE, name, url, options,
+                                 cancellable, error))
+        return FALSE;
+    }
+  else
+    {
+      /* Replace the entire option group */
+      if (!g_key_file_remove_group (remote->options, remote->group, error))
+        return FALSE;
+
+      if (g_str_has_prefix (url, "metalink="))
+        g_key_file_set_string (remote->options, remote->group, "metalink",
+                               url + strlen ("metalink="));
+      else
+        g_key_file_set_string (remote->options, remote->group, "url", url);
+
+      if (options != NULL)
+        keyfile_set_from_vardict (remote->options, remote->group, options);
+
+      /* Write out updated settings */
+      if (remote->file != NULL)
+        {
+          gsize length;
+          g_autofree char *data = g_key_file_to_data (remote->options, &length,
+                                                      NULL);
+
+          if (!g_file_replace_contents (remote->file, data, length,
+                                        NULL, FALSE, 0, NULL,
+                                        cancellable, error))
+            return FALSE;
+        }
+      else
+        {
+          g_autoptr(GKeyFile) config = ostree_repo_copy_config (self);
+
+          /* Remove the existing group if it exists */
+          if (!g_key_file_remove_group (config, remote->group, &local_error))
+            {
+              if (!g_error_matches (local_error, G_KEY_FILE_ERROR,
+                                    G_KEY_FILE_ERROR_GROUP_NOT_FOUND))
+                {
+                  g_propagate_error (error, g_steal_pointer (&local_error));
+                  return FALSE;
+                }
+            }
+
+          ot_keyfile_copy_group (remote->options, config, remote->group);
+          if (!ostree_repo_write_config (self, config, error))
+            return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
 /**
  * ostree_repo_remote_change:
  * @self: Repo
@@ -1776,6 +1858,9 @@ ostree_repo_remote_change (OstreeRepo     *self,
     case OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS:
       return impl_repo_remote_delete (self, sysroot, TRUE, name,
                                       cancellable, error);
+    case OSTREE_REPO_REMOTE_CHANGE_REPLACE:
+      return impl_repo_remote_replace (self, sysroot, name, url, options,
+                                       cancellable, error);
     }
   g_assert_not_reached ();
 }
index 007cbeddc3a78c3734d096833fda33b5dc230105..b92b70832ee0d3f8a60236ac74b75b28df13f874 100644 (file)
@@ -166,12 +166,14 @@ gboolean      ostree_repo_remote_delete (OstreeRepo     *self,
  * @OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS: Like above, but do nothing if the remote exists
  * @OSTREE_REPO_REMOTE_CHANGE_DELETE: Delete a remote
  * @OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS: Delete a remote, do nothing if the remote does not exist
+ * @OSTREE_REPO_REMOTE_CHANGE_REPLACE: Add or replace a remote (Since: 2019.1)
  */
 typedef enum {
   OSTREE_REPO_REMOTE_CHANGE_ADD,
   OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS,
   OSTREE_REPO_REMOTE_CHANGE_DELETE,
-  OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS
+  OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS,
+  OSTREE_REPO_REMOTE_CHANGE_REPLACE,
 } OstreeRepoRemoteChange;
 
 _OSTREE_PUBLIC
index 8cd61844e5d246d0e9a49a162bd75d5e5f340d95..5588116bddcd8219fe3a73203a4a29ede24ec1bb 100755 (executable)
@@ -34,7 +34,7 @@ function assertNotEquals(a, b) {
        throw new Error("assertion failed " + JSON.stringify(a) + " != " + JSON.stringify(b));
 }
 
-print('1..6')
+print('1..9')
 
 let remotesDir = Gio.File.new_for_path('remotes.d');
 remotesDir.make_directory(null);
@@ -45,6 +45,10 @@ remoteConfig.set_value('remote "foo"', 'url', 'http://foo')
 let remoteConfigFile = remotesDir.get_child('foo.conf')
 remoteConfig.save_to_file(remoteConfigFile.get_path())
 
+let remoteOptions = GLib.Variant.new('a{sv}', {
+    'branches': GLib.Variant.new('as', ['test']),
+});
+
 // Use the full Repo constructor to set remotes-config-dir
 let repoFile = Gio.File.new_for_path('repo');
 let repo = new OSTree.Repo({path: repoFile,
@@ -60,7 +64,7 @@ print("ok read-remotes-config-dir");
 
 // Adding a remote should not go in the remotes.d dir unless this is a
 // system repo or add-remotes-config-dir is set to true
-repo.remote_add('bar', 'http://bar', null, null);
+repo.remote_add('bar', 'http://bar', remoteOptions, null);
 remotes = repo.remote_list()
 assertNotEquals(remotes.indexOf('bar'), -1);
 assertEquals(remotesDir.get_child('bar.conf').query_exists(null), false);
@@ -81,7 +85,7 @@ let repoConfig = repo.copy_config();
 repoConfig.set_boolean('core', 'add-remotes-config-dir', true);
 repo.write_config(repoConfig);
 repo.reload_config(null);
-repo.remote_add('baz', 'http://baz', null, null);
+repo.remote_add('baz', 'http://baz', remoteOptions, null);
 remotes = repo.remote_list()
 assertNotEquals(remotes.indexOf('baz'), -1);
 assertEquals(remotesDir.get_child('baz.conf').query_exists(null), true);
@@ -114,3 +118,49 @@ try {
 }
 
 print("ok config-remote-in-config-dir-fails");
+
+// Replacing a non-existent remote should succeed. This should go in the
+// config dir since add-remote-config-dir is set to true above
+repo.remote_change(null, OSTree.RepoRemoteChange.REPLACE,
+                   'nonexistent', 'http://nonexistent',
+                   null, null);
+remotes = repo.remote_list();
+assertNotEquals(remotes.indexOf('nonexistent'), -1);
+assertEquals(remotesDir.get_child('nonexistent.conf').query_exists(null), true);
+
+print("ok replace-missing-remote-succeeds");
+
+// Test replacing remote options in config dir. This should remove the
+// branches setting above
+repo.remote_change(null, OSTree.RepoRemoteChange.REPLACE, 'baz',
+                   'http://baz2', null, null);
+remoteConfigFile = remotesDir.get_child('baz.conf');
+remoteConfig = GLib.KeyFile.new();
+remoteConfig.load_from_file(remoteConfigFile.get_path(),
+                            GLib.KeyFileFlags.NONE);
+assertEquals(remoteConfig.get_value('remote "baz"', 'url'), 'http://baz2');
+try {
+    remoteConfig.get_string_list('remote "baz"', 'branches');
+    throw new Error('baz remote should not have branches option');
+} catch (e) {
+    if (!(e.matches(GLib.KeyFileError, GLib.KeyFileError.KEY_NOT_FOUND)))
+        throw e;
+}
+
+print("ok replace-remote-in-config-dir");
+
+// Test replacing remote options in config file. This should remove the
+// branches setting above
+repo.remote_change(null, OSTree.RepoRemoteChange.REPLACE, 'bar',
+                   'http://bar2', null, null);
+repoConfig = repo.get_config();
+assertEquals(repoConfig.get_value('remote "bar"', 'url'), 'http://bar2');
+try {
+    repoConfig.get_string_list('remote "bar"', 'branches');
+    throw new Error('bar remote should not have branches option');
+} catch (e) {
+    if (!(e.matches(GLib.KeyFileError, GLib.KeyFileError.KEY_NOT_FOUND)))
+        throw e;
+}
+
+print("ok replace-remote-in-config-file");